package edu.uw.tcss360.group6.model;

import java.lang.IllegalArgumentException;

import java.awt.*;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
 * Author: David Sharer
 * Editor: Bill Minter
 *
 * Simulates a Weaving Draft with only two colors (one for the top and side).
 */
@SuppressWarnings("serial")
public class WeavingDraft implements java.io.Serializable {
    private static final int DEF_WIDTH = 20;
    private static final int DEF_HEIGHT = 20;
    private static final int DEF_MULTIPLICITY = 7;

    /* The horizontal values, only one per column. Bounds are 1-multiplicity */
    private final int[] horizontal;
    /* The vertical values. */
    private final int[] vertical;
    /* The mapping function from x to y values. */
    private final boolean[][] corner;

    private final int my_width;
    private final int my_height;
    private final int my_multiplicity;

    /**
     * Constructs a blank weaving draft of the given dimensions.
     * @param height The height of the draft area.
     * @param width The width of the draft area
     * @param multiplicity the number of thread(?) lines. (cols/rows)
     * @param template The template weaving draft to base this one off of
     */
    public WeavingDraft(final WeavingDraft template, final int height, final int width, final int multiplicity) {
        this(height,width,multiplicity);
        //TODO: copy over values
    }

    /**
     * Constructs a blank weaving draft of default dimensions.
     */
    public WeavingDraft() {
        this(DEF_HEIGHT,DEF_WIDTH,DEF_MULTIPLICITY);
    }

    /**
     * Constructs a blank weaving draft of the given dimensions.
     * @param height The height of the draft area.
     * @param width The width of the draft area
     * @param multiplicity the number of thread(?) lines. (cols/rows)
     */
    public WeavingDraft(final int height, final int width, final int multiplicity) {
        my_height = height;
        my_width = width;
        my_multiplicity = multiplicity;
        horizontal = new int[width];
        vertical = new int[height];
        Arrays.fill(vertical,-1); //did this to avoid having to write a separate paint component area for the first painting, Bill
        corner = new boolean[multiplicity][multiplicity];
        for(int i = 0; i < multiplicity; i++) {
            corner[i][i] = true;
        }
    }

   
    
    
    /**
     * Gets the value for a given column in the horizontal bars.
     * @param column The column.
     * @throws IndexOutOfBoundsException when invalid column is given
     * @return a value between 0 and multiplicity (inclusive) - 0 indicates no value
     */
    public int getHorizontal(final int column) {
        if(column < 0)
            throw new IndexOutOfBoundsException("Values must be >= 0.");
        if(column >= my_width)
            throw new IndexOutOfBoundsException("The canvas isn't that wide.");
        return horizontal[column];
    }

    /**
     * Gets the value for a given column in the horizontal bars.
     * @param column The column.
     * @param value The value to set for that column.
     * @throws IllegalArgumentException if value is <0 or >multiplicity
     */
    public void setHorizontal(final int column, final int value) throws IllegalArgumentException, IndexOutOfBoundsException {
        if(column < 0)
            throw new IndexOutOfBoundsException("Column values must be >= 0.");
        if(column >= my_width)
            throw new IndexOutOfBoundsException("The canvas isn't that wide.");
        if(value < 0)
            throw new IllegalArgumentException("Value must be >= 0.");
        if(value > my_multiplicity)
            throw new IllegalArgumentException("Value must be <= multiplicity.");
        horizontal[column] = value;
    }

    
    /**
     * Gets the value for a given column in the horizontal bars.
     * @param row The row.
     * @param value The value to set for that column.
     * @throws IllegalArgumentException if value is <0 or >multiplicity
     */
    public void setVertical(final int row, final int value) throws IllegalArgumentException, IndexOutOfBoundsException {
        if(row < 0)
            throw new IndexOutOfBoundsException("Column values must be >= 0.");
        if(row >= my_width)
            throw new IndexOutOfBoundsException("The canvas isn't that wide.");
        if(value < 0)
            throw new IllegalArgumentException("Value must be >= 0.");
        if(value > my_multiplicity)
            throw new IllegalArgumentException("Value must be <= multiplicity.");
        vertical[row] = value;
    }
   
    
    /**
     * Gets the value for a given column in the horizontal bars.
     * @param row The row.
     * @throws IndexOutOfBoundsException when invalid column is given
     * @return a value between 0 and multiplicity (inclusive) - 0 indicates no value
     */
    public int getVertical(final int row) {
        if(row < 0)
            throw new IndexOutOfBoundsException("Values must be >= 0.");
        if(row >= my_width)
            throw new IndexOutOfBoundsException("The canvas isn't that wide.");
        return vertical[row];
    }


    /**
     * Gets the state of an entry in the multiplicity map
     * @param horizontal The row to map to
     * @param vertical The column to map to
     * @return Does the horizontal-vertical entry map to each other?
     * @throws IllegalArgumentException if row is invalid
     */
    public boolean getMapping(final int horizontal, final int vertical) throws IllegalArgumentException {
        if(horizontal <= 0)
            throw new IllegalArgumentException("Row must be > 0.");
        if(horizontal > my_multiplicity)
            throw new IllegalArgumentException("Row must be <= multiplicity.");
        if(vertical <= 0)
            throw new IllegalArgumentException("Column must be > 0.");
        if(vertical > my_multiplicity)
            throw new IllegalArgumentException("Column must be <= multiplicity.");
        return corner[horizontal-1][vertical-1];
    }

    /**
     * Gets the set of mapping values
     * @param horizontal The row to check
     * @throws IllegalArgumentException if horizontal is invalid
     */
    public Set<Integer> getMappingSet(final int horizontal) throws IllegalArgumentException {
        if(horizontal <= 0)
            throw new IllegalArgumentException("Row must be > 0.");
        if(horizontal > my_multiplicity)
            throw new IllegalArgumentException("Row must be <= multiplicity.");

        Set<Integer> res = new HashSet<Integer>(my_multiplicity/2);
        for(int i = 0; i < my_multiplicity; i++) {
            if(horizontal > 0 && corner[horizontal-1][i]) {
                res.add(i+1);
            }
        }
        return res;
    }

    /**
     * Adds a mapping to the draft.
     * @param horizontal The row to map to
     * @param vertical The column to map to
     * @throws IllegalArgumentException if row is invalid
     */
    public void addMapping(final int horizontal, final int vertical) throws IllegalArgumentException {
        if(horizontal <= 0)
            throw new IllegalArgumentException("Row must be > 0.");
        if(horizontal > my_multiplicity)
            throw new IllegalArgumentException("Row must be <= multiplicity.");
        if(vertical <= 0)
            throw new IllegalArgumentException("Column must be >= 0.");
        if(vertical > my_multiplicity)
            throw new IllegalArgumentException("Column must be <= multiplicity.");
        corner[horizontal-1][vertical-1] = true;
    }

    /**
     * toggles whether a mapping is included in the draft.
     * @param horizontal The row to map to
     * @param vertical The column to map to
     * @throws IllegalArgumentException if row is invalid
     */
    public void toggleMapping(final int horizontal, final int vertical) throws IllegalArgumentException {
        if(horizontal <= 0)
            throw new IllegalArgumentException("Row must be > 0.");
        if(horizontal > my_multiplicity)
            throw new IllegalArgumentException("Row must be <= multiplicity.");
        if(vertical <= 0)
            throw new IllegalArgumentException("Column must be >= 0.");
        if(vertical > my_multiplicity)
            throw new IllegalArgumentException("Column must be <= multiplicity.");
        corner[horizontal-1][vertical-1] = !corner[horizontal-1][vertical-1];
    }
    
    /**
     * Removes a mapping from the draft.
     * @param horizontal The row to map to
     * @param vertical The column to map to
     * @throws IllegalArgumentException if row is invalid
     */
    public void removeMapping(final int horizontal, final int vertical) throws IllegalArgumentException {
        if(horizontal <= 0)
            throw new IllegalArgumentException("Row must be > 0.");
        if(horizontal > my_multiplicity)
            throw new IllegalArgumentException("Row must be <= multiplicity.");
        if(vertical <= 0)
            throw new IllegalArgumentException("Column must be >= 0.");
        if(vertical > my_multiplicity)
            throw new IllegalArgumentException("Column must be <= multiplicity.");
        corner[horizontal-1][vertical-1] = false;
    }

    private ColorSource getOutputSource(final int horizontal, final int vertical) throws IndexOutOfBoundsException {
        if(horizontal < 0)
            throw new IndexOutOfBoundsException("Column values must be >= 0.");
        if(horizontal >= my_width)
            throw new IndexOutOfBoundsException("The canvas isn't that wide.");
        if(vertical < 0)
            throw new IndexOutOfBoundsException("Row values must be >= 0.");
        if(vertical >= my_height)
            throw new IndexOutOfBoundsException("The canvas isn't that tall.");

        int index_h = getHorizontal(horizontal);
        int index_v = getVertical(vertical);

        if(index_h == 0)
            return ColorSource.SIDE; //unset -> default color
        //index_h--; //offset for indexing -not needed
        try {
            for(Integer i : getMappingSet(index_h)) {
                if(index_v == i)
                    return ColorSource.TOP;
            }
            return ColorSource.SIDE;

        } catch (Exception e) { //this should never happen
            throw new RuntimeException(e);
        }
    }

    public Color getOutputColor(final int horizontal, final int vertical) throws IndexOutOfBoundsException {
        //TODO: Add variable colors
        if( getOutputSource(horizontal,vertical) == ColorSource.TOP)
            return Color.RED.darker();
        else
            return Color.BLACK;
    }

    public int getWidth() {
        return my_width;
    }

    public int getHeight() {
        return my_height;
    }

    public int getMultiplicity() {
        return my_multiplicity;
    }

    private enum ColorSource {
        TOP,
        SIDE;
    }
}
